import {
  MAIN,
  PRESET,
  RESULT,
  parse,
  parseStatus,
  parseControl,
  parseIndent,
  parseBoxes,
  parseFlexes,
} from './parse'
import { stylePresets } from './presets'
import { seed, getValidIndexes } from '../planClass/raw/math'

import type { MagizTypes } from '../../types/magizTypes'
import type { StyleTypes } from '../../types/styleTypes'
import type { StyleParsed } from '../../types/styleParsed'

export { MagizStyles }

/** 用于管理多个样式文件的样式库类 */
class MagizStyles {
  /** 默认样式 Blocks */
  Blocks: StyleTypes.BuildingStyle
  /** 整合后的样式参数 */
  data: StyleTypes.Styles

  /** 创建样式库实例，输入的样式将自动整合 */
  constructor(
    /** 输入多个样式参数并整合 */
    stylesArray: StyleTypes.Styles[]
  ) {
    this.Blocks = {
      type: 'FREE',
      tags: {},
      section: {
        bottom: {
          height: '1H',
          floors: [{ ctrl: { total: 1 }, extrude: [{ height: '1H' }] }],
        },
      },
    }
    this.data = {}
    this.merge(stylesArray)
  }

  merge(stylesArray: StyleTypes.Styles[]) {
    stylesArray.forEach((style) => {
      for (const key in style) {
        if (this.data[key]) console.warn(`Style overwrited: ${key}`)
        this.data[key] = style[key]!
      }
    })
  }

  /** 输入注册状态，检查样式是否可用 */
  isValid(name: string, regState: boolean) {
    if (name === 'Blocks') {
      return true
    } else {
      const found = this.data[name]
      return found && (found.type === 'FREE' || regState) ? true : false
    }
  }

  getNames() {
    return Object.keys(this.data)
  }

  /** 根据输入参数和随机种子解析样式。优先按custom解析 */
  parseStyle(styleParams: MagizTypes.StyleParams): StyleParsed.Result {
    // 确保由用户输入的参数为有效的值
    const styleSelected = this.data[styleParams.style || 'Blocks']
    const height = Math.abs(Number(styleParams.height || 0))
    const floorHeight = Math.abs(Number(styleParams.floorHeight) || 3)
    const elevation = Number(styleParams.elevation || 0)
    seed.set(Number(styleParams.seed))

    // 清除全局缓存的解析结果
    RESULT.colors = []
    RESULT.floorCount = 0
    RESULT.classified = {}

    if (styleSelected) {
      // 初始化内解析公式所需的单位
      MAIN.UNIT = Object.assign({ H: height, FH: floorHeight, SH: 0 }, styleSelected.unit)

      // 根据分段生成参数分配三段式的高度
      let tsh = 0
      let msh = 0
      let bsh = 0
      const ss = styleSelected.section
      const tfh = parse(ss.top?.floorHeight, floorHeight)
      const mfh = parse(ss.middle?.floorHeight, floorHeight)
      let bfh = parse(ss.bottom.floorHeight, floorHeight)
      switch (styleParams.part) {
        case 'T':
          tsh = height
          parseSection('top', ss, elevation, tsh, matchFloorHeight(tsh, tfh))
          break
        case 'M':
          msh = height
          parseSection('middle', ss, elevation, msh, matchFloorHeight(msh, mfh))
          break
        case 'B':
          bsh = height
          parseSection('bottom', ss, elevation, bsh, matchFloorHeight(bsh, bfh))
          break
        case 'TM':
          // 中部高度按层数拟合
          msh = matchSectionHeight(height - parse(ss.top?.height), mfh)
          // 顶部最终按中部确定
          tsh = height - msh
          parseSection('middle', ss, elevation, msh, mfh)
          parseSection('top', ss, elevation + height - tsh, tsh, tfh)
          break
        case 'MB':
          // 中部高度按层数拟合
          msh = matchSectionHeight(height - parse(ss.bottom.height), mfh)
          // 底部最终按中部确定
          bsh = height - msh
          parseSection('bottom', ss, elevation, bsh, bfh)
          parseSection('middle', ss, elevation + bsh, msh, mfh)
          break
        default:
          // 顶部高度按参数确定
          tsh = parse(ss.top?.height)
          // 中部高度按层数拟合
          msh = matchSectionHeight(height - tsh - parse(ss.bottom.height), mfh)
          // 底部高度和层高最终根据中部拟合高度确定
          bsh = height - tsh - msh
          // 重新拟合底部层高
          bfh = matchFloorHeight(bsh, bfh)
          parseSection('bottom', ss, elevation, bsh, bfh)
          parseSection('middle', ss, elevation + bsh, msh, mfh)
          parseSection('top', ss, elevation + height - tsh, tsh, tfh)
      }
    } else {
      console.warn(`style name "${styleParams.style}" is invalid.`)
    }

    // 解析完成后清理 custom
    return RESULT
  }
}

//////////////////////////////////////////////////////////
function matchFloorHeight(h: number, fh: number) {
  return h / Math.floor(h / fh)
}

/** 按原始高度和层高计算实际的段高 */
function matchSectionHeight(h: number, fh: number) {
  return Math.floor(h / fh) * fh
}

/** 解析样式的段，须调用 styles  */
function parseSection(
  type: keyof StyleTypes.BuildingStyle['section'],
  sectionParams: StyleTypes.BuildingStyle['section'],
  sectionElevation: number,
  sectionHeight: number,
  floorHeight: number
) {
  const section = sectionParams[type]
  // 顶部可能因不计入总高导致其段高为0，因此不检查段高
  if (section) {
    MAIN.UNIT.SH = sectionHeight
    MAIN.UNIT.FH = floorHeight
    let floorCount = 0
    if (type === 'top') {
      floorCount = 1
    } else {
      floorCount = Math.round(sectionHeight / floorHeight)
      RESULT.floorCount += floorCount
    }

    if (floorCount > 0) {
      section.floors?.forEach((floorParams) => {
        parseFloor(floorCount, floorHeight, sectionElevation, floorParams)
      })

      section.presets?.forEach((presetParams) => {
        const { name, params } = presetParams
        const { floor, unit, color } = stylePresets[name as keyof typeof stylePresets]
        const inputUnit = Object.assign({ ...unit }, params?.unit)
        const inputColor = Object.assign({ ...color }, params?.color)

        // 将预设的单位和颜色缓存到全局变量中
        let uk: keyof typeof inputUnit
        let ck: keyof typeof inputColor
        for (uk in inputUnit) {
          PRESET.UNIT[uk] = parse(inputUnit[uk])
        }
        for (ck in color) {
          PRESET.COLOR[ck] = inputColor[ck]!
        }

        // 解析参数
        floor.forEach((params) => parseFloor(floorCount, floorHeight, sectionElevation, params))

        // 结束时清理PRESET缓存
        PRESET.UNIT = {}
        PRESET.COLOR = {}
      })
    }
  }
}

//////////////// INSIDE FUNCTIONS BELOW ////////////////

function parseFloor(
  /** 根据段高和层高拟合计算的层数 */
  totalFloors: number,
  floorHeight: number,
  sectionElevation: number,
  floorParams: StyleTypes.FloorParams
) {
  // 获取将生成元素的标高
  const elevations = getValidIndexes(totalFloors, parseControl(floorParams.ctrl)).map(
    (i) => sectionElevation + i * floorHeight
  )

  // 格式化边线控制参数序列
  const edgeParams: StyleParsed.HandleEdge[] = []
  floorParams.edge?.forEach((HandleEdge) => {
    const { offset, along, clamp, indent, split } = HandleEdge
    if (offset) {
      edgeParams.push({ offset: parseOffsetEdge(offset) })
    } else if (along) {
      edgeParams.push({ along })
    } else if (clamp) {
      edgeParams.push({ clamp: parseClamp(clamp)! })
    } else if (indent) {
      edgeParams.push({ indent: parseIndent(indent)! })
    } else if (split) {
      edgeParams.push({ split: parseSplit(split) })
    }
  })

  const edgeParamsJSON = JSON.stringify(edgeParams)
  const saveAs: StyleParsed.FloorResult = {
    diverse: parse(floorParams.diverse),
    elevations,
    edgeParams,
    extrude: [],
    match: [],
    vertical: [],
    horizontal: [],
    appendent: [],
    boundingBox: [],
    slopingRoof: [],
  }
  const sameEdgeParams = RESULT.classified[edgeParamsJSON]
  sameEdgeParams
    ? sameEdgeParams.parsed.push(saveAs)
    : (RESULT.classified[edgeParamsJSON] = { params: edgeParams, parsed: [saveAs] })

  parseExtrude(saveAs, floorParams)
  parseMatch(saveAs, floorParams)
  parseVertical(saveAs, floorParams)
  parseHorizontal(saveAs, floorParams)
  parseAppendent(saveAs, floorParams)
  parseSlopingRoof(saveAs, floorParams)
  parseBoundingBox(saveAs, floorParams)
}

/** 解析偏移边线参数 */
function parseOffsetEdge(params: StyleTypes.HandleEdge['offset']): StyleParsed.OffsetType {
  if (typeof params === 'object') {
    return {
      x: parse(params.x),
      y: parse(params.y),
      asRatio: parse(params.asRatio),
    }
  } else {
    const x = parse(params)
    return { x, y: x, asRatio: 0 }
  }
}

function parseExtrude(to: StyleParsed.FloorResult, params?: StyleTypes.FloorParams) {
  params?.extrude?.forEach((p) => {
    to.extrude.push(
      parseStatus(p, {
        height: parse(p.height),
        thickness: parse(p.thickness),
        along: p.along || 'WIDTH',
      })
    )
  })
}

function parseMatch(to: StyleParsed.FloorResult, params?: StyleTypes.FloorParams) {
  params?.match?.forEach((p) => {
    to.match.push({
      array: p.array.map((u) => {
        return parseStatus(u, {
          count: parse(u.count, 1),
          unitDepth: parse(u.unitDepth),
          flexHeight: parse(u.flexHeight),
          shrink: parseIndent(u.shrink),
        })
      }),
      along: p.along || 'WIDTH',
      ctrlMatch: parseControl(p.ctrl),
      sandwich: parse(p.sandwich),
      simplify: parse(p.simplify),
    })
  })
}

function parseVertical(to: StyleParsed.FloorResult, params?: StyleTypes.FloorParams) {
  params?.vertical?.forEach((p) => {
    to.vertical.push({
      array: p.array.map((unit) => {
        const { replace } = unit
        return {
          space: parse(unit.space),
          boxes: parseBoxes(unit.boxes),
          flexes: parseFlexes(unit.flexes),
          replace: replace
            ? {
                chance: parse(replace.chance),
                boxes: parseBoxes(replace.boxes),
                flexes: parseFlexes(replace.flexes),
              }
            : undefined,
          count: parse(unit.count, 1),
        }
      }),
      ctrlArray: parseControl(p.ctrl),
      sandwich: parse(p.sandwich),
      endWidth: parse(p.endWidth),
      diverse: parse(p.diverse),
    })
  })
}

function parseHorizontal(to: StyleParsed.FloorResult, params?: StyleTypes.FloorParams) {
  params?.horizontal?.forEach((p) => {
    to.horizontal.push(
      parseStatus(p, {
        array: p.array.map(parse),
        flexDepth: parse(p.flexDepth),
        flexHeight: parse(p.flexHeight),
        seg: parse(p.seg),
        sandwich: parse(p.sandwich),
        ctrlFlex: parseControl(p.ctrl),
        shrink: parseIndent(p.shrink),
      })
    )
  })
}

function parseSlopingRoof(to: StyleParsed.FloorResult, params?: StyleTypes.FloorParams) {
  params?.slopingRoof?.forEach((p) => {
    to.slopingRoof.push(
      parseStatus(p, {
        shape: p.shape,
        overhang: parse(p.overhang),
        height: parse(p.height),
      })
    )
  })
}

function parseBoundingBox(to: StyleParsed.FloorResult, params?: StyleTypes.FloorParams) {
  params?.boundingBox?.forEach((p) => {
    to.boundingBox.push(
      parseStatus(p, {
        height: parse(p.height),
        clamp: parseClamp(p.clamp),
      })
    )
  })
}

/** 解析 floor.adjunct */
function parseAppendent(to: StyleParsed.FloorResult, params?: StyleTypes.FloorParams) {
  params?.appendent?.forEach((p) => {
    to.appendent.push({
      count: parse(p.count, 1),
      place: p.place || 'EDGE',
      boxes: parseBoxes(p.boxes),
    })
  })
}

function parseClamp(params?: StyleTypes.ClampType): StyleParsed.ClampType | undefined {
  return params
    ? {
        startX: parse(params.startX),
        startY: parse(params.startY),
        centralX: parse(params.centralX),
        endX: parse(params.endX),
        endY: parse(params.endY),
        centralY: parse(params.centralY),
        asRatio: parse(params.asRatio),
        reverse: parse(params.reverse),
      }
    : undefined
}

function parseSplit(params: NonNullable<StyleTypes.HandleEdge['split']>): StyleParsed.SplitType {
  return {
    array: params.array.map(parse),
    select: params.select,
    sandwich: parse(params.sandwich),
  }
}
